#include "Encoder.h"
#include <iostream>

Encoder::Encoder()
{ //paar dingen initialiseren
	videopts = 0;
	gEncVideoConvertFrame = avcodec_alloc_frame();
	gEncVideoConvertCtx = NULL;
	gEncAudioFifoOutBuf = NULL;

}

void Encoder::EnableStreams(bool p_VideoEnabled, bool p_AudioEnabled)
{ //streams enablen of disablen
	VideoEnabled = p_VideoEnabled;
	AudioEnabled = p_AudioEnabled;
}

void Encoder::SetupEncoder(char *filename, CodecID videoCodec, CodecID audioCodec)
{ // encoder al een beetje invullen ,een nieuwe videostream+audiostream maken
	name = filename;
	if ((fmt = guess_format( NULL, filename, NULL )) == NULL) //format initialiseren
		throw("VideoEncoder: Could not guess format codec\n");
	if ((out_format_context = av_alloc_format_context()) == NULL)
		throw("VideoEncoder: Can't allocate output format context.\n");

	if(fmt->audio_codec == CODEC_ID_AAC){ //AAC wil niet lukken, dus zet het maar op AC3
		fmt->audio_codec = CODEC_ID_AC3;
	}
	out_format_context->oformat = fmt;
	if(VideoEnabled)//als er video in output moet zitten: initialiseer een nieuwe video stream
		MakeNewVideoStream(videoCodec);

	if(AudioEnabled)//als er audio in output moet zitten: initialiseer een nieuwe audio stream
		MakeNewAudioStream(audioCodec);
}

void Encoder::MakeNewVideoStream(CodecID videoCodec)
{
	if ((video_stream = av_new_stream(out_format_context, 0)) == NULL)
		throw("VideoEncoder:Can't add video stream to output file.\n");
	video_codec_context = video_stream->codec;
	video_codec_context->codec_id = videoCodec;
	video_codec_context->codec_type = CODEC_TYPE_VIDEO;
}

void Encoder::MakeNewAudioStream(CodecID audioCodec)
{
	if ((audio_stream = av_new_stream(out_format_context, 0)) == NULL)
		throw("VideoEncoder:Can't add audio stream to output file.\n");
	audio_codec_context = audio_stream->codec;
	audio_codec_context->codec_id = audioCodec;
	audio_codec_context->codec_type = CODEC_TYPE_AUDIO;
}
void Encoder::EncodeVideo(AVFrame *frame)
{ 
	frame->pts = videopts; //stel PTS in
	std::cout << "Current Frame: " << frame->pts << "\r"; //print dat uit
	sws_scale(gEncVideoConvertCtx, frame->data, frame->linesize, INPUT_WIDTH, INPUT_HEIGHT, gEncVideoConvertFrame->data, gEncVideoConvertFrame->linesize); //dit is dus u Frame scalen naar nieuwe resolutie
	gEncVideoConvertFrame->pts = videopts; //nog eens pts instellen voor de zekerheid

	int len = avcodec_encode_video ( video_codec_context, video_outbuf, video_outbuf_size, gEncVideoConvertFrame );
	//encoderen
	if ( len >= 0 ) 
	{ //als er geencodeert is
		AVPacket out_packet; //packet openen voor het schrijven
		av_init_packet( &out_packet ); //initialiseren
		out_packet.pts = av_rescale_q(videopts, video_codec_context->time_base, video_stream->time_base); 
		//u pts rescalen ten opzichte van de output timebase...
		if ( video_codec_context->coded_frame->key_frame )
			out_packet.flags |= PKT_FLAG_KEY;
		out_packet.stream_index = video_stream->index; //stream index vast zetten
		out_packet.data = video_outbuf; //data kopieren
		out_packet.size = len; //lengte kopieren
		av_write_frame( out_format_context, &out_packet ); //uitschrijven naar file
		videopts++; //pts teller optellen
	}
}

void Encoder::EncodeAudio( int16_t* samples, int buffersize)
{
		int		nBytesToWrite = 0;
		uint8_t *	pRawSamples = NULL;
		int		nAudioFrameSizeOut = audio_codec_context->frame_size * audio_codec_context->channels * sizeof(int16_t);
		nBytesToWrite = buffersize;
		pRawSamples  = (uint8_t*)samples;
		av_fifo_write(&gEncAudioFifo, pRawSamples, nBytesToWrite); //fifo buffer vullen
		while (av_fifo_read(&gEncAudioFifo, gEncAudioFifoOutBuf, nAudioFrameSizeOut) == 0)
		{ //als er genoeg samples in de buffer zitten, encodeer 
			int len = avcodec_encode_audio ( audio_codec_context, audio_outbuf, audio_outbuf_size, 
				(int16_t*)gEncAudioFifoOutBuf ); //encoderen
			if ( len >= 0 ) 
			{ //als er geencodeert is
				AVPacket out_packet; //packet openen voor het schrijven
				av_init_packet( &out_packet ); //initialiseren
				out_packet.flags |= PKT_FLAG_KEY;
				out_packet.stream_index = audio_stream->index; //stream index vast zetten
				out_packet.data = audio_outbuf; //data kopieren
				out_packet.size = len; //lengte kopieren
				av_write_frame( out_format_context, &out_packet ); // uitschrijven naar file
			}
		}
}
void Encoder::OpenOutput()
{ //open van output file
	SearchCodec(); //zoeken naar codec
	url_fopen( &out_format_context->pb, name, URL_WRONLY ); //file openen

	if(av_write_header( out_format_context ) != 0)
		throw("Error Writing Header"); //header schrijven
}
void Encoder::CloseOutput()
{ //fileke sluiten
	if(av_write_trailer( out_format_context ) != 0) //eerst trailer schrijven
		throw("Error Writing Trailer");
	url_fclose( &out_format_context->pb ); //sluiten van file
}

void Encoder::SearchCodec()
{ //codec zoeken van de outputfile & openen
	if(VideoEnabled)// als video aanstaat in output
		OpenVideoCodec();
	if(AudioEnabled) // als audio aanstaat in output
		OpenAudioCodec();
}

void Encoder::OpenVideoCodec()
{
	video_codec = avcodec_find_encoder ( video_codec_context->codec_id );
	if(video_codec==NULL)
	{
		throw("Unsupported Output Video Codec\n");
	}
	else if(avcodec_open ( video_codec_context, video_codec ) < 0)
		{
			throw("Could not open Output Video Codec\n");
		}
	avcodec_open ( video_codec_context, video_codec );
	video_outbuf_size = 400000;
	video_outbuf = (uint8_t*) av_malloc( video_outbuf_size );
}

void Encoder::OpenAudioCodec()
{
	audio_codec = avcodec_find_encoder ( audio_codec_context->codec_id );
	if(audio_codec==NULL)
	{
		throw("Unsupported Output Audio Codec\n");
	}
	else if(avcodec_open ( audio_codec_context, audio_codec ) < 0)
		{
			throw("Could not open Ouput Audio Codec\n");
		}
	audio_outbuf_size = 400000;
	audio_outbuf = (uint8_t*) av_malloc( audio_outbuf_size );	
}
void Encoder::FifoInitialise()
{
		av_fifo_init(&gEncAudioFifo, 2 * AVCODEC_MAX_AUDIO_FRAME_SIZE);
		if ((gEncAudioFifoOutBuf = (uint8_t*)av_malloc(2 * AVCODEC_MAX_AUDIO_FRAME_SIZE)) == NULL)
		{
			throw("Can't allocate buffer to read into from audio FIFO.\n");
		}
}
void Encoder::FillParameters(int bitrate, int den, int num, int gopsize, int width, int height, int sample_rate, int audio_bitrate, int channels)
{ //paramaters voor de Encoder invullen, hier moet ook audio nog bij
	if(VideoEnabled)
		FillVideoParameters(bitrate, den, num, gopsize, width, height);
	if(AudioEnabled)
		FillAudioParameters(sample_rate, audio_bitrate, channels);

	av_set_parameters(out_format_context, NULL);
}

void Encoder::FillVideoParameters(int bitrate, int den, int num, int gopsize, int width, int height)
{
	video_codec_context->bit_rate = bitrate;
	video_codec_context->time_base.den = den;
	video_codec_context->time_base.num = num;
	video_codec_context->gop_size = gopsize;
	video_codec_context->pix_fmt = PIX_FMT_YUV420P;
	video_codec_context->width = width;
	video_codec_context->height = height;
}
void Encoder::FillAudioParameters(int sample_rate, int audio_bitrate, int channels)
{
	audio_codec_context->time_base.den = audio_bitrate;
	audio_codec_context->time_base.num = 1;
	audio_codec_context->bit_rate = audio_bitrate;
	audio_codec_context->sample_rate = sample_rate;
	audio_codec_context->channels = channels;
}
void Encoder::SetConvertParameters(AVCodecContext* codec_context)
{ //de Scaler instellen

	if(VideoEnabled)
	{
		gEncVideoConvertCtx = sws_getContext(codec_context->width,codec_context->height, codec_context->pix_fmt, 
			video_codec_context->width, video_codec_context->height, video_codec_context->pix_fmt, SWS_FAST_BILINEAR,
			NULL, NULL, NULL); //convert context instellen
		INPUT_HEIGHT = codec_context->height;
		INPUT_WIDTH = codec_context->width;

		int numBytes=avpicture_get_size(PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height);
		uint8_t *buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
		avpicture_fill((AVPicture *)gEncVideoConvertFrame, buffer, PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height); //leeg frame vullen
	}
	
}

void Encoder::FillBlankVideo(int timeframe)
{

	int temppts = 0;
	while(temppts < timeframe*video_codec_context->time_base.den)
	{ //zolang het in de timeframe is, encodeer lege frames
		//maak lege frames aan
		AVFrame *frame = avcodec_alloc_frame();
		int numBytes=avpicture_get_size(PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height);
		uint8_t* buffer = (uint8_t*)av_malloc(numBytes);

		gEncVideoConvertCtx = sws_getContext(video_codec_context->width,video_codec_context->height, video_codec_context->pix_fmt, video_codec_context->width, video_codec_context->height, video_codec_context->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);		
		INPUT_HEIGHT = video_codec_context->height;
		INPUT_WIDTH = video_codec_context->width;
		avpicture_fill((AVPicture *)frame, buffer, PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height);
		memset(frame->data[0], 0, frame->linesize[0]*video_codec_context->height);
		memset(frame->data[1], 128, frame->linesize[1]*video_codec_context->height/2);
		memset(frame->data[2], 128, frame->linesize[2]*video_codec_context->height/2); 
		EncodeVideo(frame); //geef door aan encode functie
		temppts++;
	}
}

void Encoder::FillBlankAudio(int timeframe)
{
	int totalAudio = 0;
	//maak lege audio aan
	int samplebuffer_size = audio_codec_context->frame_size * 2 *audio_codec_context->channels;
	uint8_t* buffer = (uint8_t*)av_malloc(samplebuffer_size);

	memset(buffer, 0, samplebuffer_size);
	while(totalAudio <= 2 * timeframe * audio_codec_context->sample_rate * audio_codec_context->channels)
	{ //zolang het in de timeframe is, encodeer lege audio
		EncodeAudio((int16_t*)buffer, samplebuffer_size);
		totalAudio += samplebuffer_size;
	}

}